/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.vfs.provider.tar;
import java.io.File;
import java.util.Date;
import java.util.Locale;
/**
* This class represents an entry in a Tar archive. It consists of the entry's
* header, as well as the entry's File. Entries can be instantiated in one of
* three ways, depending on how they are to be used. <p>
*
* TarEntries that are created from the header bytes read from an archive are
* instantiated with the TarEntry( byte[] ) constructor. These entries will be
* used when extracting from or listing the contents of an archive. These
* entries have their header filled in using the header bytes. They also set the
* File to null, since they reference an archive entry not a file. <p>
*
* TarEntries that are created from Files that are to be written into an archive
* are instantiated with the TarEntry( File ) constructor. These entries have
* their header filled in using the File's information. They also keep a
* reference to the File for convenience when writing entries. <p>
*
* Finally, TarEntries can be constructed from nothing but a name. This allows
* the programmer to construct the entry by hand, for instance when only an
* InputStream is available for writing to the archive, and the header
* information is constructed from other information. In this case the header
* fields are set to defaults and the File is set to null. <p>
*
* The C structure for a Tar Entry's header is: <pre>
* struct header {
* char name[NAMSIZ];
* char mode[8];
* char uid[8];
* char gid[8];
* char size[12];
* char mtime[12];
* char chksum[8];
* char linkflag;
* char linkname[NAMSIZ];
* char magic[8];
* char uname[TUNMLEN];
* char gname[TGNMLEN];
* char devmajor[8];
* char devminor[8];
* } header;
* </pre>
*
* @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @author <a href="mailto:peter@apache.org">Peter Donald</a>
* @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
* @see TarInputStream
* @see TarOutputStream
*/
class TarEntry
{
/**
* The length of the name field in a header buffer.
*/
public static final int NAMELEN = 100;
/**
* The entry's modification time.
*/
private int m_checkSum;
/**
* The entry's group name.
*/
private int m_devMajor;
/**
* The entry's major device number.
*/
private int m_devMinor;
/**
* The entry's minor device number.
*/
private File m_file;
/**
* The entry's user id.
*/
private int m_groupID;
/**
* The entry's user name.
*/
private StringBuffer m_groupName;
/**
* The entry's checksum.
*/
private byte m_linkFlag;
/**
* The entry's link flag.
*/
private StringBuffer m_linkName;
/**
* The entry's link name.
*/
private StringBuffer m_magic;
/**
* The entry's size.
*/
private long m_modTime;
/**
* The entry's name.
*/
private int m_mode;
private StringBuffer m_name;
/**
* The entry's group id.
*/
private long m_size;
/**
* The entry's permission mode.
*/
private int m_userID;
/**
* The entry's magic tag.
*/
private StringBuffer m_userName;
/**
* Construct an entry with only a name. This allows the programmer to
* construct the entry's header "by hand". File is set to null.
*
* @param name the name of the entry
*/
TarEntry( final String name )
{
this();
final boolean isDir = name.endsWith( "/" );
m_name = new StringBuffer( name );
m_mode = isDir ? 040755 : 0100644;
m_linkFlag = isDir ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
m_modTime = ( new Date() ).getTime() / 1000;
m_linkName = new StringBuffer( "" );
m_userName = new StringBuffer( "" );
m_groupName = new StringBuffer( "" );
}
/**
* Construct an entry with a name an a link flag.
*
* @param name Description of Parameter
* @param linkFlag Description of Parameter
*/
TarEntry( final String name, final byte linkFlag )
{
this( name );
m_linkFlag = linkFlag;
}
/**
* Construct an entry for a file. File is set to file, and the header is
* constructed from information from the file.
*
* @param file The file that the entry represents.
*/
TarEntry( final File file )
{
this();
m_file = file;
String name = file.getPath();
// Strip off drive letters!
final String osName =
System.getProperty( "os.name" ).toLowerCase( Locale.US );
if( -1 != osName.indexOf( "netware" ) )
{
if( name.length() > 2 )
{
final char ch1 = name.charAt( 0 );
final char ch2 = name.charAt( 1 );
if( ch2 == ':' &&
( ( ch1 >= 'a' && ch1 <= 'z' ) ||
( ch1 >= 'A' && ch1 <= 'Z' ) ) )
{
name = name.substring( 2 );
}
}
}
else if( -1 != osName.indexOf( "netware" ) )
{
final int colon = name.indexOf( ':' );
if( colon != -1 )
{
name = name.substring( colon + 1 );
}
}
name = name.replace( File.separatorChar, '/' );
// No absolute pathnames
// Windows (and Posix?) paths can start with "\\NetworkDrive\",
// so we loop on starting /'s.
while( name.startsWith( "/" ) )
{
name = name.substring( 1 );
}
m_linkName = new StringBuffer( "" );
m_name = new StringBuffer( name );
if( file.isDirectory() )
{
m_mode = 040755;
m_linkFlag = TarConstants.LF_DIR;
if( m_name.charAt( m_name.length() - 1 ) != '/' )
{
m_name.append( "/" );
}
}
else
{
m_mode = 0100644;
m_linkFlag = TarConstants.LF_NORMAL;
}
m_size = file.length();
m_modTime = file.lastModified() / 1000;
m_checkSum = 0;
m_devMajor = 0;
m_devMinor = 0;
}
/**
* Construct an entry from an archive's header bytes. File is set to null.
*
* @param header The header bytes from a tar archive entry.
*/
TarEntry( final byte[] header )
{
this();
parseTarHeader( header );
}
/**
* Construct an empty entry and prepares the header values.
*/
private TarEntry()
{
m_magic = new StringBuffer( TarConstants.TMAGIC );
m_name = new StringBuffer();
m_linkName = new StringBuffer();
String user = System.getProperty( "user.name", "" );
if( user.length() > 31 )
{
user = user.substring( 0, 31 );
}
m_userName = new StringBuffer( user );
m_groupName = new StringBuffer( "" );
}
/**
* Set this entry's group id.
*
* @param groupId This entry's new group id.
*/
public void setGroupID( final int groupId )
{
m_groupID = groupId;
}
/**
* Set this entry's group id.
*
* @param groupId This entry's new group id.
* @deprecated Use setGroupID() instead
* @see #setGroupID(int)
*/
public void setGroupId( final int groupId )
{
m_groupID = groupId;
}
/**
* Set this entry's group name.
*
* @param groupName This entry's new group name.
*/
public void setGroupName( final String groupName )
{
m_groupName = new StringBuffer( groupName );
}
/**
* Set this entry's modification time. The parameter passed to this method
* is in "Java time".
*
* @param time This entry's new modification time.
*/
public void setModTime( final long time )
{
m_modTime = time / 1000;
}
/**
* Set this entry's modification time.
*
* @param time This entry's new modification time.
*/
public void setModTime( final Date time )
{
m_modTime = time.getTime() / 1000;
}
/**
* Set the mode for this entry
*
* @param mode The new Mode value
*/
public void setMode( final int mode )
{
m_mode = mode;
}
/**
* Set this entry's name.
*
* @param name This entry's new name.
*/
public void setName( final String name )
{
m_name = new StringBuffer( name );
}
/**
* Set this entry's file size.
*
* @param size This entry's new file size.
*/
public void setSize( final long size )
{
m_size = size;
}
/**
* Set this entry's user id.
*
* @param userId This entry's new user id.
*/
public void setUserID( final int userId )
{
m_userID = userId;
}
/**
* Set this entry's user id.
*
* @param userId This entry's new user id.
* @deprecated Use setUserID() instead
* @see #setUserID(int)
*/
public void setUserId( final int userId )
{
m_userID = userId;
}
/**
* Set this entry's user name.
*
* @param userName This entry's new user name.
*/
public void setUserName( final String userName )
{
m_userName = new StringBuffer( userName );
}
/**
* If this entry represents a file, and the file is a directory, return an
* array of TarEntries for this entry's children.
*
* @return An array of TarEntry's for this entry's children.
*/
public TarEntry[] getDirectoryEntries()
{
if( null == m_file || !m_file.isDirectory() )
{
return new TarEntry[ 0 ];
}
final String[] list = m_file.list();
final TarEntry[] result = new TarEntry[ list.length ];
for( int i = 0; i < list.length; ++i )
{
result[ i ] = new TarEntry( new File( m_file, list[ i ] ) );
}
return result;
}
/**
* Get this entry's file.
*
* @return This entry's file.
*/
public File getFile()
{
return m_file;
}
/**
* Get this entry's group id.
*
* @return This entry's group id.
* @deprecated Use getGroupID() instead
* @see #getGroupID()
*/
public int getGroupId()
{
return m_groupID;
}
/**
* Get this entry's group id.
*
* @return This entry's group id.
*/
public int getGroupID()
{
return m_groupID;
}
/**
* Get this entry's group name.
*
* @return This entry's group name.
*/
public String getGroupName()
{
return m_groupName.toString();
}
/**
* Set this entry's modification time.
*
* @return The ModTime value
*/
public Date getModTime()
{
return new Date( m_modTime * 1000 );
}
/**
* Get this entry's mode.
*
* @return This entry's mode.
*/
public int getMode()
{
return m_mode;
}
/**
* Get this entry's name.
*
* @return This entry's name.
*/
public String getName()
{
return m_name.toString();
}
/**
* Get this entry's file size.
*
* @return This entry's file size.
*/
public long getSize()
{
return m_size;
}
/**
* Get this entry's checksum.
*
* @return This entry's checksum.
*/
public int getCheckSum()
{
return m_checkSum;
}
/**
* Get this entry's user id.
*
* @return This entry's user id.
* @deprecated Use getUserID() instead
* @see #getUserID()
*/
public int getUserId()
{
return m_userID;
}
/**
* Get this entry's user id.
*
* @return This entry's user id.
*/
public int getUserID()
{
return m_userID;
}
/**
* Get this entry's user name.
*
* @return This entry's user name.
*/
public String getUserName()
{
return m_userName.toString();
}
/**
* Determine if the given entry is a descendant of this entry. Descendancy
* is determined by the name of the descendant starting with this entry's
* name.
*
* @param desc Entry to be checked as a descendent of
* @return True if entry is a descendant of
*/
public boolean isDescendent( final TarEntry desc )
{
return desc.getName().startsWith( getName() );
}
/**
* Return whether or not this entry represents a directory.
*
* @return True if this entry is a directory.
*/
public boolean isDirectory()
{
if( m_file != null )
{
return m_file.isDirectory();
}
if( m_linkFlag == TarConstants.LF_DIR )
{
return true;
}
if( getName().endsWith( "/" ) )
{
return true;
}
return false;
}
/**
* Indicate if this entry is a GNU long name block
*
* @return true if this is a long name extension provided by GNU tar
*/
public boolean isGNULongNameEntry()
{
return m_linkFlag == TarConstants.LF_GNUTYPE_LONGNAME &&
m_name.toString().equals( TarConstants.GNU_LONGLINK );
}
/**
* Determine if the two entries are equal. Equality is determined by the
* header names being equal.
*
* @param other Entry to be checked for equality.
* @return True if the entries are equal.
*/
public boolean equals( final TarEntry other )
{
return getName().equals( other.getName() );
}
/**
* Parse an entry's header information from a header buffer.
*
* @param header The tar entry header buffer to get information from.
*/
private void parseTarHeader( final byte[] header )
{
int offset = 0;
m_name = TarUtils.parseName( header, offset, NAMELEN );
offset += NAMELEN;
m_mode = (int)TarUtils.parseOctal( header, offset, TarConstants.MODELEN );
offset += TarConstants.MODELEN;
m_userID = (int)TarUtils.parseOctal( header, offset, TarConstants.UIDLEN );
offset += TarConstants.UIDLEN;
m_groupID = (int)TarUtils.parseOctal( header, offset, TarConstants.GIDLEN );
offset += TarConstants.GIDLEN;
m_size = TarUtils.parseOctal( header, offset, TarConstants.SIZELEN );
offset += TarConstants.SIZELEN;
m_modTime = TarUtils.parseOctal( header, offset, TarConstants.MODTIMELEN );
offset += TarConstants.MODTIMELEN;
m_checkSum = (int)TarUtils.parseOctal( header, offset, TarConstants.CHKSUMLEN );
offset += TarConstants.CHKSUMLEN;
m_linkFlag = header[ offset++ ];
m_linkName = TarUtils.parseName( header, offset, NAMELEN );
offset += NAMELEN;
m_magic = TarUtils.parseName( header, offset, TarConstants.MAGICLEN );
offset += TarConstants.MAGICLEN;
m_userName = TarUtils.parseName( header, offset, TarConstants.UNAMELEN );
offset += TarConstants.UNAMELEN;
m_groupName = TarUtils.parseName( header, offset, TarConstants.GNAMELEN );
offset += TarConstants.GNAMELEN;
m_devMajor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
offset += TarConstants.DEVLEN;
m_devMinor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
}
/**
* Write an entry's header information to a header buffer.
*
* @param buffer The tar entry header buffer to fill in.
*/
public void writeEntryHeader( final byte[] buffer )
{
int offset = 0;
offset = TarUtils.getNameBytes( m_name, buffer, offset, NAMELEN );
offset = TarUtils.getOctalBytes( m_mode, buffer, offset, TarConstants.MODELEN );
offset = TarUtils.getOctalBytes( m_userID, buffer, offset, TarConstants.UIDLEN );
offset = TarUtils.getOctalBytes( m_groupID, buffer, offset, TarConstants.GIDLEN );
offset = TarUtils.getLongOctalBytes( m_size, buffer, offset, TarConstants.SIZELEN );
offset = TarUtils.getLongOctalBytes( m_modTime, buffer, offset, TarConstants.MODTIMELEN );
final int checkSumOffset = offset;
for( int i = 0; i < TarConstants.CHKSUMLEN; ++i )
{
buffer[ offset++ ] = (byte)' ';
}
buffer[ offset++ ] = m_linkFlag;
offset = TarUtils.getNameBytes( m_linkName, buffer, offset, NAMELEN );
offset = TarUtils.getNameBytes( m_magic, buffer, offset, TarConstants.MAGICLEN );
offset = TarUtils.getNameBytes( m_userName, buffer, offset, TarConstants.UNAMELEN );
offset = TarUtils.getNameBytes( m_groupName, buffer, offset, TarConstants.GNAMELEN );
offset = TarUtils.getOctalBytes( m_devMajor, buffer, offset, TarConstants.DEVLEN );
offset = TarUtils.getOctalBytes( m_devMinor, buffer, offset, TarConstants.DEVLEN );
while( offset < buffer.length )
{
buffer[ offset++ ] = 0;
}
final long checkSum = TarUtils.computeCheckSum( buffer );
TarUtils.getCheckSumOctalBytes( checkSum, buffer, checkSumOffset, TarConstants.CHKSUMLEN );
}
}